const GraphEventUtils = {
  /**是否为为实体元素 */
  isVertex(cell) {
    return cell && cell.value instanceof Object
  },
  /**是否为为关系线元素 */
  isRelation(cell) {
    return cell && cell.isEdge()
  },

  /**是否为为图框元素 */
  isDiagramCell(cell) {
    return cell && cell.id === '3'
  },

  /**获取display */
  getDisplay(cell) {
    return cell && cell.getAttribute('display')
  },
}

const PortList = [...window.portList, 'SYSML_PARAMETRIC_YUESHUCANSHUJIEDIAN']

/** 用于处理元素新增/删除/移动时，图框的大小自适应 */
class DiagramSizeWather {
  constructor(editorUi) {
    this.editorUi = editorUi
    this.graph = this.editorUi.editor.graph

    this.padding = 10 // 元素离图框的padding

    const maxPoint = this.getMaxBoundWithCells(this.boundControlCells)

    const update = parent._.debounce(this.updateDiagramCellGeometry.bind(this), 50, {leading: true})
    this.maxPoint = new Proxy(maxPoint, { // 最右(下)侧cell的x(y)的点
      set: (target, prototy, value) => {
        target[prototy] = value
        update()
        return true
      }
    })
  }

  get diagramCell() {
    return this.graph.backgroundPage
  }

  // 获取控制画布大下的元素列表
  get boundControlCells() {
    return Object.values(this.graph.model.cells).filter(i => !PortList.includes(GraphEventUtils.getDisplay(i)))
  }

  getMaxBoundWithCells(cells) {
    // 得到新增的元素所处的最远位置
    const maxBound = [0, 0]
    cells.forEach(cell => {
      if (!cell || !cell.geometry || cell.id === '3') return
      const geo = cell.geometry
      geo.height = Number(geo.height)
      geo.width = Number(geo.width)

      if (cell.value instanceof Object) {
        let display = cell.value.getAttribute('display')
        // 3.活动参数贴边，贴边了  则不影响图框大小
        if (display && ['SYSML_PARAMETRIC_YUESHUSHUXINGJIEDIAN', 'SYSML_ACTIVITY_HUODONGCANSHUJIEDIAN'].includes(display)) {
          if (this.graph.fixedOutLine(cell)) return
        }
        const ztree = window.zTreeObj
        let currentTreeNode = ztree?.getSelectedNodes()[0]?.modelType

        // 内部模块图下，端口会自动贴边，不影响图框的位置
        if (currentTreeNode && window.INTERNALMODULE.includes(currentTreeNode) && display && PortList.includes(display)) return
      }
      if(cell.isEdge()) {
        const state = this.graph.view.getState(cell)
        if(!state) return
        maxBound[0] = Math.max(maxBound[0], state.cellBounds.x + state.cellBounds.width)
        maxBound[1] = Math.max(maxBound[1], state.cellBounds.y + state.cellBounds.height)
      }else {
        let x = geo.x
        let y = geo.y
        // 处理Offset
        if (geo.relative && cell.parent) {
          const parentGeo = cell.parent.geometry
          if(parentGeo) {
            x = (geo.offset?.x || 0) + geo.x * parentGeo.width
            y = (geo.offset?.y || 0) + geo.y * parentGeo.height
          }
        }
        maxBound[0] = Math.max(maxBound[0], x + geo.width)
        maxBound[1] = Math.max(maxBound[1], y + geo.height)
      }

    })

    return maxBound
  }

  /** 新增了元素 */
  cellsAdded(cells) {
    // 得到新增的元素所处的最远位置
    const maxBound = this.getMaxBoundWithCells(cells)

    if (maxBound[0] > this.maxPoint[0]) {
      this.maxPoint[0] = maxBound[0]
    }
    if (maxBound[1] > this.maxPoint[1]) {
      this.maxPoint[1] = maxBound[1]
    }
  }

  /** 删除了元素 */
  cellsRemoved(cells) {
    // 得到删除的元素所处的最远位置
    const maxBound = this.getMaxBoundWithCells(cells)

    // 正好删除了边缘的元素，重新计算
    if (maxBound[0] >= this.maxPoint[0] && maxBound[1] >= this.maxPoint[1]) {
      this.refreshBound()
    }
  }

  /** 更新 最新的图框大小 */
  refreshBound() {
    const bound = this.getMaxBoundWithCells(this.boundControlCells)

    this.maxPoint[0] = bound[0]
    this.maxPoint[1] = bound[1]
  }

  /** 更新图框的大小 */
  updateDiagramCellGeometry() {
    // 视口区域
    const viewContainer = this.graph.container
    const clientWidth = viewContainer.clientWidth - this.padding
    const clientHeight = viewContainer.clientHeight - this.padding

    const width = Math.max(this.maxPoint[0] + this.padding, clientWidth)
    const height = Math.max(this.maxPoint[1] + this.padding, clientHeight)
    const geometry = this.graph.getModel().getGeometry(this.diagramCell)
    if (!geometry) return
    if (geometry.width === width && geometry.height === height) return
    window.isSetingGraphXml = true;
    try {
      geometry.width = width
      geometry.height = height
      Object.values(this.graph.model.cells).forEach(cell => {
        myEditor.graphEvent.repositionPort(cell)
      })
    } finally {
      setTimeout(() => {
        window.isSetingGraphXml = false;
        this.graph.view.revalidate();
      }, 10)
    }
  }
}


GraphEvent = function (editorUi) {
  this.editorUi = editorUi;
  this.graph = this.editorUi.editor.graph;
  this.tooltip = null
  this.highlight = new mxCellHighlight(this.graph, '#0075ff', 3)

  this.enabled = true// 当前鼠标下的元素是否可操作
  this.portList = PortList

  this.init();

  this.diagramSizeWather = new DiagramSizeWather(editorUi)

  this.currentState = null
  this.utils = GraphEventUtils

  this.isAddingEdge = false
  this.reWriteConnectHandleMouseUp()
  this.isConnecting = false
};


GraphEvent.prototype.init = function () {
  const graphEvent = this
  this.graph.addMouseListener({
    mouseDown: function (sender, me) {
      graphEvent.mouseDown.call(this, sender, me, graphEvent)
    },
    mouseMove: function (sender, me) {
      graphEvent.mouseMove.call(this, sender, me, graphEvent)
    },
    mouseUp: function (sender, me) {
      // 当处于连接状态时，鼠标右键，触发esc退出创建
      if (mxEvent.isRightMouseButton(me.evt) && graphEvent.graph.connectionHandler.isConnecting()) {
        myEditor.keyHandler.escape(me.evt)
      }
      graphEvent.mouseUp.call(this, sender, me, graphEvent)
    },
    dragEnter: function (evt, state) {
      graphEvent.dragEnter.call(this, evt, state, graphEvent)
    },
    dragLeave: function (evt, state) {
      graphEvent.dragLeave.call(this, evt, state, graphEvent)
    },
  })
  const model = this.graph.model
  model.addListener(mxEvent.CHANGE, graphEvent.graphChange.bind(graphEvent));
  model.addListener(mxEvent.BEFORE_UNDO, graphEvent.beforeUndo.bind(this))
  this.graph.addListener(mxEvent.CELLS_ADDED, graphEvent.cellAdded.bind(graphEvent));
  this.graph.addListener(mxEvent.CELLS_MOVED, graphEvent.cellsMoved.bind(graphEvent));
  this.graph.addListener(mxEvent.CELLS_REMOVED, graphEvent.cellsRemoved.bind(graphEvent));
  this.graph.addListener(mxEvent.RESIZE_CELLS, graphEvent.cellsResize.bind(graphEvent));

  this.graph.addListener(mxEvent.LABEL_CHANGED, function (sender, me) {
    // 思维导图编辑后，更改编辑状态
    var cell = me.getProperty('cell');
    var style = sender.getCellStyle(cell);

    if ((cell.value instanceof Object && cell.value.getAttribute('display') === 'LABEL_ZHUTI') || style.shape === 'label') {
      setTimeout(function () {
        myEditor.editor.setModified(true);
      })
    }
  });

  const oldConnectMouseDown = mxConnectionHandler.prototype.mouseDown
  mxConnectionHandler.prototype.mouseDown = function() {
    const edge = this.graph.selectingCell
    if(edge && edge.isEdge()) {
      if (GraphEventUtils.isVertex(edge)) {
        // 根据display获取对应的style，保存到edge中，用于连线的样式
        edge.value.setAttribute('style', edge.style)
        window.edgeValue = edge.value
      } else if (typeof edgeValue === 'string') {
        window.edgeValue = edge.style
      }
      window.mxConnectioCell = edge;
    }
    oldConnectMouseDown.apply(this, arguments)
  }

  this.editorUi.editor.addListener('resetGraphView', mxUtils.bind(this, function () {
    this.graph.selectingCell = null;
  }))
}

// graphChange一共有这些类型
const changeTypes = {
  mxStyleChange: mxStyleChange, // 样式改变
  mxGeometryChange: mxGeometryChange, // 位置大小改变
  mxChildChange: mxChildChange, // 新增删除
  mxValueChange: mxValueChange, // label改变
  mxRootChange: mxRootChange, // 根节点改变
  mxTerminalChange: mxTerminalChange, // 连接元素改变
  mxVisibleChange: mxVisibleChange, // 显示隐藏改变
  mxCellAttributeChange: mxCellAttributeChange, // cell的attribute改变
}

const getChangeType = (item) => {
  let type = 'unknown'
  for (const key in changeTypes) {
    if (changeTypes.hasOwnProperty(key) && item instanceof changeTypes[key]) {
      return key
    }
  }
  return type
}
/**
 * 写在beforeUndo而不是graphChange，可以兼容原有的撤销回滚功能
 * @param sender
 * @param evt
 */
GraphEvent.prototype.beforeUndo = function (sender, evt) {
  const graph = this.graph
  graph.model.beginUpdate()
  try {
    const edit = evt.getProperty('edit')
    console.log('beforeUndo', edit.changes)
    const movedCellIds = []
    const addedCellIds = []
    for (let i = edit.changes.length - 1; i >= 0; i--) {
      const change = edit.changes[i]
      const type = getChangeType(change)
      if (type === 'mxGeometryChange') {

        !movedCellIds.includes(change.cell.id) && movedCellIds.push(change.cell.id)
        // setTimeout(()=> {
        //   const cells = (function loop(cell, temp) {
        //     temp.push(cell)
        //     cell.children?.forEach(i => {
        //       i.geometry.relative && loop(i, temp)
        //     })
        //     return temp
        //   })(change.cell, [])
          /** .... */
          // console.log(cells);

          // const isCellExceeded = cells.some((cell) => graph.isCellExceeded(cell))
          // 超出画布边框，回到上一个位置
          // if (isCellExceeded) {
          //   console.log('over');

            // // graph.model.setGeometry 可能再次触发 beforeUndo造成循环调用
            // change.cell.setGeometry(change.previous.clone());
            // graph.refresh();
          // }
        // },0)
      } else if (type === 'mxChildChange') {
        !change.previous && !addedCellIds.includes(change.child.id) && addedCellIds.push(change.child.id)
      }
    }
    const addedCells = addedCellIds.map(id => graph.model.getCell(id))
    const changedCells = movedCellIds.reduce((acc, id) => {
      if (!addedCellIds.includes(id)) acc.push(graph.model.getCell(id))
      return acc
    }, addedCells)

    changedCells.forEach(cell => {
      this.repositionPort(cell)
    })

  } finally {
    graph.model.endUpdate()
  }
}
/**
 * @description 监听graph有改变时
 * @param {Object} model graph上的model对象
 * @param {Object} mxEventObject 改变的event事件对象
 */
GraphEvent.prototype.graphChange = function (model, mxEventObject) {
  const graph = myEditor.editor.graph
  if (!graph.backgroundPage) return

    let isChange = false // 记录是否触发了变化，如果变化了，需要refresh

    if (window.isSysml || window.isGuideModel) {
      graph.setGridEnabled(false)
    }

    const movedCellIds = []
    const addedCellIds = []
    for (let i = 0; i < mxEventObject.properties.changes.length; i++) {
      const change = mxEventObject.properties.changes[i]
      const type = getChangeType(change)
      const cell = change.cell || change.child
      if (type === 'mxStyleChange' || !cell?.geometry) {
        continue
      }
      const display = GraphEventUtils.getDisplay(cell)
      // 更新需求的关系
      if (display === 'SYSML_REQUIREMENT_XQJD' && ['mxChildChange', 'mxRootChange'].includes(type)) {
        window.drawioBus && window.drawioBus.$emit('updateRequirementLabel', cell)
      }
      if (display === 'LINE_NO_SOURCE' && cell.isEdge()) {
        if (!cell.source) {
          cell.style = cell.style.replace('strokeWidth=5', 'strokeWidth=1')
            .replace('strokeColor=#ff0000', 'strokeColor=#000000');
          graph.view.invalidate(cell);
        }
      }
      // 需求的关系线
      if (cell.value.nodeName === 'relation') {
        if (GraphEventUtils.getDisplay(cell.source) === 'SYSML_REQUIREMENT_XQJD') {
          window.drawioBus && window.drawioBus.$emit('updateRequirementLabel', cell.source)
        }
        if (GraphEventUtils.getDisplay(cell.target) === 'SYSML_REQUIREMENT_XQJD') {
          window.drawioBus && window.drawioBus.$emit('updateRequirementLabel', cell.target)
        }
        // // 动态更新多重性在关系线的位置
        // if(cell.lineTargetShowName || cell.lineSourceShowName) {
        //   const util = new utils()
        //   util.setRelationMultiplicity(cell)
        // }
      }

      // 动态更新多重性在关系线的位置
      cell.edges?.forEach(i => {
        if (i.value.nodeName === 'relation' && (i.lineTargetShowName || i.lineSourceShowName)) {
          const util = new utils()
          util.setRelationMultiplicity(i)
          isChange = true
        }
      })

      if (type === 'mxGeometryChange') {
        !movedCellIds.includes(change.cell.id) && movedCellIds.push(change.cell.id)
        this.fitChildren(change.cell);
      } else if (type === 'mxChildChange') {
        !change.previous && !addedCellIds.includes(change.child.id) && addedCellIds.push(change.child.id)
      }
    }

    const addedCells = addedCellIds.map(id => graph.model.getCell(id))

    if (movedCellIds.length) {
      this.diagramSizeWather.refreshBound()
    } else if (addedCellIds.length) {
      this.diagramSizeWather.cellsAdded(addedCells)
      this.exitAddPattern()
    } else if (isChange) {
      graph.view.revalidate();
    }
}

function getStyleObject(cell) {
  const positionArr = ['verticalLabelPosition', 'labelPosition', 'align', 'verticalAlign', 'noLabel'];
  const style = cell.style;
  const arr = style.split(';');
  let styleObj = {};
  arr.forEach(item => {
    let [key, value] = item.split('=');
    if (key && value) {
      styleObj[key] = value;
    }
  });

  positionArr.forEach(key => {
    if (!(key in styleObj)) {
      if (key === 'noLabel') {
        styleObj[key] = 0;
      } else {
        styleObj[key] = 'center';
      }
    }
  });
  return styleObj;
};

/**
 * 处理端口自适应位置
 * */
GraphEvent.prototype.repositionPort = function (cell) {
  const display = GraphEventUtils.getDisplay(cell)
  if (!cell || !cell.geometry || !display || !this.portList.includes(display)) return false
  const parent = cell.parent

  // 1.处于具体的元素上
  if (parent && parent.id !== '3' && parent.id !== '1') {
    // offset x
    if (cell.geometry.relative) {
      cell.geometry.x = (cell.geometry.offset.x + cell.geometry.x * parent.geometry.width) / parent.geometry.width
      cell.geometry.y = (cell.geometry.offset.y + cell.geometry.y * parent.geometry.height) / parent.geometry.height
    } else {
      cell.geometry.x = cell.geometry.x / parent.geometry.width
      cell.geometry.y = cell.geometry.y / parent.geometry.height
      cell.geometry.relative = true; // 手动置为relative
    }
    const offset = this.graph.setPortOffset(parent, cell);
    cell.geometry.x = offset[0];
    cell.geometry.y = offset[1];
    cell.geometry.offset = new mxPoint(offset[2], offset[3]);
    cell.position = offset[4];
  }

  const ztree = window.zTreeObj
  let currentTreeNode = ztree?.getSelectedNodes()[0]?.modelType

  // 2. 处于 内部模块图的画布上
  if (parent && parent.id === '1' && window.INTERNALMODULE.includes(currentTreeNode)) {
    const geometry = this.graph.setPortPosition(this.graph.backgroundPage, cell)
    cell.geometry.x = geometry[0]
    cell.geometry.y = geometry[1]
    cell.position = geometry[2]
  }

  if (cell.position === 'left') {
    if (parent && parent.geometry && parent.geometry.x < 60) {
      parent.geometry.x = (cell.value.getAttribute("label").length + 2) * 16 + 15
    }
  }
  if (cell.position === 'top') {
    if (parent && parent.geometry && parent.geometry.y < 60) {
      parent.geometry.y = 60
    }
  }

  const style = {
    top: {
      verticalLabelPosition: 'top',
      labelPosition: 'center',
      align: 'center',
      verticalAlign: 'bottom',
    },
    bottom: {
      verticalLabelPosition: 'bottom',
      labelPosition: 'center',
      align: 'center',
      verticalAlign: 'top',
    },
    left: {
      verticalLabelPosition: 'middle',
      labelPosition: 'left',
      align: 'right',
      verticalAlign: 'middle',
    },
    right: {
      verticalLabelPosition: 'middle',
      labelPosition: 'right',
      align: 'left',
      verticalAlign: 'middle',
    },
  };

  const filed = getStyleObject(cell);
  Object.keys(filed).forEach(key => {
    if (cell.position && style[cell.position]) {
      this.graph.setCellStyles(key, style[cell.position][key] ? style[cell.position][key] : filed[key], [cell]);
    }
  });

  // 流端口，设置箭头
  if (['SYSML_INTERNALMODULE_DAILIPORTJIEDIAN', 'SYSML_BLOCKDEFINITION_DAILIPORTJIEDIAN'].includes(display)) {
    this.graph.setDailtPortDirection(cell)
  }
  return true
}

GraphEvent.prototype.cellAdded = function (sender, evt) {
  var cells = evt.getProperty('cells');
  const cell = cells[0]
  let isChange = false

  // 如果是通用里面普通的线，就让他在点击处创建,普通线设置坐标位置是根据sourcePoint和targetPoint来定位
  if (cell.edge && !cell.value && cell.style.includes('endArrow=')) {
    const geometry = cell.geometry;
    const x = geometry.x;
    const y = geometry.y;
    const sourcePoint = geometry.sourcePoint;
    const targetPoint = geometry.targetPoint;
    cell.geometry.sourcePoint.x = x + sourcePoint.x
    cell.geometry.sourcePoint.y = y + sourcePoint.y
    cell.geometry.targetPoint.x = x + targetPoint.x
    cell.geometry.targetPoint.y = y + targetPoint.y
    isChange = true
  }

  if (cell.value instanceof Object && cell.value.nodeName === 'relation') {
    if (cell.source && cell.source.toBeRemoved === 'toBeRemoved') {
      this.removeToBeRemoved(window.myEditor.editor.graph, cell, [cell.source], 'source')
      return
    }
    if (cell.target && cell.target.toBeRemoved === 'toBeRemoved') {
      this.removeToBeRemoved(window.myEditor.editor.graph, cell, [cell.target], 'target')
      return
    }

    if (zTreeObj.currentSelectedNode.modelType === 'DIAGRAM_IDEF0') {
      window.drawioBus && window.drawioBus.$emit('IdefoSelect', cells[0])
    }
  }

  window.myEditor.graphEvent.exitAddPattern()
  if (isChange) {
    const state = this.graph.view.getState(cell)
    state && (state.invalid = true)
  }
}

/**
 * @description 监听移动
 * @param {Object} graph graph上的model对象
 * @param {Object} mxEventObject 改变的event事件对象
 */
GraphEvent.prototype.cellsMoved = function (graph, mxEventObject) {
  var cells = mxEventObject.getProperty('cells')
  if (!cells.length) return;
}

GraphEvent.prototype.startConnect = function (graph, me, state, edge) {
  if (this.isConnecting !== false) return

  if (GraphEventUtils.isVertex(edge)) {
    // 根据display获取对应的style，保存到edge中，用于连线的样式
    edge.value.setAttribute('style', edge.style)
    window.edgeValue = edge.value
  } else if (typeof edgeValue === 'string') {
    window.edgeValue = edge.style
  }

  window.mxConnectioCell = edge;

  function getConnectPosition(pt) {
    const s = graph.view.scale;

    const translateX = pt.x - graph.view.translate.x * s - state.cell.geometry.x
    const translateY = pt.y - graph.view.translate.y * s - state.cell.geometry.y
    let x = 0
    let y = 0
    if (translateX > state.cell.geometry.width / 2) {
      x = state.cell.geometry.x + state.cell.geometry.width
    } else {
      x = state.cell.geometry.x
    }

    if (translateY > state.cell.geometry.height / 2) {
      y = state.cell.geometry.y + state.cell.geometry.height
    } else {
      y = state.cell.geometry.y
    }
    return {
      x,
      y
    }
  }

  var pt = mxUtils.convertPoint(graph.container,
    mxEvent.getClientX(me.evt), mxEvent.getClientY(me.evt));

  if (pt && state) {
    graph.evtPointY = (pt.y - state.y) / (state.height)
  }

  var c = graph.getPointForEvent(me.evt);
  const translate = graph.view.getTranslate()

  !graph.connectionHandler.currentPoint && (graph.connectionHandler.currentPoint = c);
  graph.connectionHandler.updateCurrentState(me, new mxPoint(translate.x + c.x, translate.y + c.y))

  if (!graph.connectionHandler.isStartEvent(me)) {
    const position = getConnectPosition(pt)
    graph.connectionHandler.start(state, position.x, position.y);
  } else {
    graph.connectionHandler.mouseDown(graph, me)
  }


  graph.isMouseDown = true;
  graph.isMouseTrigger = mxEvent.isMouseEvent(me.evt);

  mxEvent.consume(me.evt);
}


GraphEvent.prototype.allowCellContain = function allowCellContain(target, cell) {
  // 非框架配置的可以互相包含，而与框架配置的不行
  if (!target?.isElement() || !cell.isElement()) {
    return !cell.isElement() && !target?.isElement()
  }
  // quote元素取defineId，普通取metaId
  // 因为引用元素创建的时候，该元素可能是其他模型的，metaId不是正确的需要从后端查接口，所以此情况取defineId
  return ['metaId', 'defineId'].some((key) => window.containRules[cell.getAttribute(key)]?.includes(target.getAttribute(key)));
}

/**
 * 判断两元素是否可连线
 * @param {mxCell | null} target
 * @param {mxCell} cell
 * @return {boolean}
 * */
GraphEvent.prototype.allowCellRelation = function allowCellRelation(target, cell) {
  // 非entity线连接所有
  if (!cell.isElement()) return true
  // entity线不可连接普通
  if (!target?.isElement()) return false
  const connectionHandler = this.graph.connectionHandler
  const isConnecting = connectionHandler.isConnecting()
  const getRelationRules = (source, line, key) => window.relationRules[source.getAttribute(key)]?.[line.getAttribute(key)]
  // 引用元素会先创建获取真实metaId后再创建连线，所以此处其实无需取defineId，但为了统一，还是保留以下写法
  const relationKeys = ['metaId', 'defineId']
  // 正在连接中，检查鼠标下元素
  if (isConnecting) {
    const previousCell = connectionHandler.previous.cell // 当前正在连接的源cell
    if (!previousCell || previousCell.id === cell.id) return false // 自己不能连接自己
    return relationKeys.some(key => getRelationRules(previousCell, cell, key)?.includes(target.getAttribute(key)));
  } else {
    // 关系，检查可连接
    return relationKeys.some(key => getRelationRules(target, cell, key));
  }
}

/**
 * 校验元素是否能添加到画布
 * @param {mxCell} target
 * @param {mxCell} cell
 * @param {{[key:string]: any} & {showMessage?: boolean}} [opts]
 * */
GraphEvent.prototype.allowCellInsert = function (target, cell, opts) {
  // 未在具体的元素上
  const inRootTarget = !target || ['1', '3'].includes(target.id)
  const modelType = window.zTreeObj?.currentSelectedNode?.modelType

  // 非线entity 包含
  if (!cell.isEdge()) {
    // 在模块定义图，端口只能创建在对于的块上
    // 1。处于 内部模块图的画布上
    // 2。处于containRules定义的元素上
    if (cell.isElement() && this.portList.includes(cell.getAttribute('display')) && inRootTarget) {
      return window.INTERNALMODULE.includes(modelType)
    }

    return inRootTarget ? true : this.allowCellContain(target, cell)
  } else {
    return inRootTarget ? false : this.allowCellRelation(target, cell)
  }
}

/**
 * @description 在画布上直接创建元素
 */
GraphEvent.prototype.addCellOnPage = function (me, currentState) {
  const graphEvent = this
  const selectingCell = graphEvent.graph.selectingCell

  if (!this.allowCellInsert(currentState.cell, selectingCell, {showMessage: true})) return;

  // 直接生成
  const cloneCells = graphEvent.graph.cloneCells([selectingCell]);
  let {x: pointX, y: pointY} = this.graph.getPointForEvent(me.evt)

  if (currentState.cellBounds.width > selectingCell.geometry.width) {
    const overX = (pointX + selectingCell.geometry.width) - (currentState.cellBounds.width + currentState.cellBounds.x)
    const overY = (pointY + selectingCell.geometry.height) - (currentState.cellBounds.height + currentState.cellBounds.y)
    if (overX > 0) {
      pointX -= overX
    }
    if (overY > 0) {
      pointY -= overY
    }
  }
  // 减去父节点的x y
  cloneCells[0].geometry.x = Math.max(0, pointX - currentState.cellBounds.x)
  cloneCells[0].geometry.y = Math.max(0, pointY - currentState.cellBounds.y)

  let addedCell;

  this.graph.getModel().beginUpdate();
  try {
    addedCell = this.graph.addCells(cloneCells, undefined, undefined, undefined, undefined, undefined, true)

    if (addedCell.length && typeof (addedCell[0].value) == 'object' && addedCell[0].value != null && addedCell[0].value.nodeName == 'quote') {
      graphEvent.graph.setSelectionCells(addedCell);

      // 打开引用窗口
      myEditor.actions.get('toggleQuote').funct();
    }
    if (!mxEvent.isShiftDown(me.evt)) {
      window.myEditor.editor.graph.fireEvent(new mxEventObject('cellsInserted', 'cells', addedCell));
    }
    graphEvent.graph.fireMouseEvent(mxEvent.MOUSE_UP, me)

    this.dragLeave(me.getEvent(), currentState, graphEvent);

    graphEvent.graph.getRubberband().reset()
    graphEvent.graph.clearSelection()

    graphEvent.graph.getSelectionModel().clear()
  } finally {
    this.graph.getModel().endUpdate();
  }
  graphEvent.exitAddPattern()
  if (window.isPokePatttern === false) {
    graphEvent.graph.selectingCell = null
  }
  graphEvent.currentState = null;
}

/**
 * @description 重新拦截连线的mouseup方法
 */
GraphEvent.prototype.reWriteConnectHandleMouseUp = function() {
  const oldMouseUpHandle = mxConnectionHandler.prototype.mouseUp;

  const graphEvent = this;
  mxConnectionHandler.prototype.mouseUp = function(_graph, me) {
    if (!graphEvent.isAddingEdge) return oldMouseUpHandle.apply(this, arguments);
    try {
      const graph = myEditor.editor.graph;
      if (!graphEvent.enabled) return;

      const selectingCell = graph.selectingCell;
      let addedCell = me.sourceState ? me.sourceState.cell : null;

      if (GraphEventUtils.isRelation(selectingCell) && (!addedCell || GraphEventUtils.isDiagramCell(addedCell))) {
        // 特殊处理IDEF0图情况
        if (zTreeObj.currentSelectedNode.modelType === 'DIAGRAM_IDEF0') {
          addedCell = graphEvent.createToBeRemovedCell(graph, me)[0];
        } else if (graph.connectionHandler?.previous?.cell) {
          oldMouseUpHandle.call(this, _graph, me);
          return;
        }
      }

      if (addedCell && graphEvent.getMouseEnterEnabled(addedCell)) {
        if (this.isConnecting() === true && addedCell.isConnectable()) {
          this.connect(this.previous.cell, addedCell, me.evt, null);
          this.destroyIcons();
          mxEvent.consume(me.evt);
          graphEvent.exitAddPattern();
          this.reset();

          if (window.isPokePatttern === false) {
            graph.selectingCell = null;
          }

          graphEvent.dragLeave.call(null, null, null, graphEvent);
        }
      }
    } finally {
      graphEvent.isAddingEdge = false;
    }
  };
};


/**
 * @description 添加关系
 */
GraphEvent.prototype.addEdge = function (me, currentState) {
  const graphEvent = this
  const offsetY = 0
  const edge = graphEvent.graph.selectingCell
  if (!graphEvent.enabled || !edge) return
  if (myEditor.editor.graph.connectionHandler.isConnecting() === false) {
    // 开始建立关系
    graphEvent.startConnect(graphEvent.graph, me, currentState, edge, offsetY)
    this.isAddingEdge = true
  }
}

/**
 * @description 添加成子节点，组成包含关系
 */
GraphEvent.prototype.addChildrenNode = function (me, currentState) {
  const graphEvent = this
  const selectingCell = this.graph.selectingCell
  if (!this.enabled || !selectingCell || !currentState) return

  this.graph.getModel().beginUpdate();
  try {
    const cloneCells = this.graph.cloneCells([selectingCell]);

    let {x: pointX, y: pointY} = this.graph.getPointForEvent(me.evt)

    if (currentState.cellBounds.width > selectingCell.geometry.width) {
      const overX = (pointX + selectingCell.geometry.width) - (currentState.cellBounds.width + currentState.cellBounds.x)
      const overY = (pointY + selectingCell.geometry.height) - (currentState.cellBounds.height + currentState.cellBounds.y)
      if (overX > 0) {
        pointX -= overX
      }
      if (overY > 0) {
        pointY -= overY
      }
    }
    // 减去父节点的x y
    cloneCells[0].geometry.x = pointX - currentState.cellBounds.x
    cloneCells[0].geometry.y = pointY - currentState.cellBounds.y


    const addedCell = window.myEditor.editor.graph.addCells(cloneCells, currentState.cell, undefined, undefined, undefined, undefined, true)

    if (addedCell.length && typeof (addedCell[0].value) == 'object' && addedCell[0].value != null && addedCell[0].value.nodeName == 'quote') {
      this.graph.setSelectionCells(addedCell);
      // 打开引用窗口
      myEditor.actions.get('toggleQuote').funct();

      this.graph.fireMouseEvent(mxEvent.MOUSE_UP, me)

      this.dragLeave(me.getEvent(), graphEvent.currentState, this);

      this.graph.getRubberband().reset()
      this.graph.clearSelection()

      this.graph.getSelectionModel().clear()
    }

    if (window.isPokePatttern === false) {
      this.graph.selectingCell = null
    }
  } finally {
    this.graph.getModel().endUpdate()
  }
}

GraphEvent.prototype.mouseDown = function (sender, me, graphEvent) {
}
GraphEvent.prototype.mouseMove = function (sender, me, graphEvent) {
  const state = me.getState()
  if (state && state.cell.style.indexOf('group') > -1) return

  if (graphEvent.currentState != null && (state == graphEvent.currentState ||
    state == null)) {
    var tol = iconTolerance;
    const tmp = new mxRectangle(me.getGraphX() - tol,
      me.getGraphY() - tol, 2 * tol, 2 * tol);

    if (mxUtils.intersects(tmp, graphEvent.currentState)) {
      return;
    }
  }

  let tmp = state

  // Ignores everything but vertices
  if ((tmp != null && !graphEvent.isValidInsertTarget(tmp.cell))) {
    tmp = null;
  }

  if (tmp != graphEvent.currentState) {
    if (graphEvent.currentState != null) {
      this.dragLeave(me.getEvent(), graphEvent.currentState, graphEvent);
    }

    graphEvent.currentState = tmp;
    if (graphEvent.currentState != null) {
      this.dragEnter(me.getEvent(), graphEvent.currentState);
    }
  }
}

// 是否允许被连线或包含；初次过滤非法目标
GraphEvent.prototype.isValidInsertTarget = function (cell) {
  return this.graph.model.isVertex(cell)
}

/**
 * @param sender {Graph}
 * @param me {mxEventObject}
 * @param graphEvent {GraphEvent}
 */
GraphEvent.prototype.mouseUp = function (sender, me, graphEvent) {
  if (me.evt.which === 3) return // 按下的鼠标中键
  const selectingCell = graphEvent.graph.selectingCell
  const currentCell = graphEvent.currentState?.cell
  // 特殊处理IDEF0图情况
  if (graphEvent.utils.isRelation(selectingCell) && graphEvent.utils.isDiagramCell(currentCell)) {
    if (zTreeObj.currentSelectedNode.modelType === 'DIAGRAM_IDEF0') {
      graphEvent.createAutoConnect(graphEvent, me)
      return
    }
  }
  /**
   * isAddingStatus： 处于创建状态
   * selectingCell:   左边sidebar点击的cell
   * currentCell：   点击下的目标cell
   */
  if (!(window.isAddingStatus && selectingCell)) return

  selectingCell.geometry.width = Number(selectingCell.geometry.width)
  selectingCell.geometry.height = Number(selectingCell.geometry.height)

  /**
   * 端口不能直接添加到画布
   */
  if (GraphEventUtils.isDiagramCell(currentCell) && selectingCell.edge === false) {
    // 在画布上创建
    graphEvent.addCellOnPage(me, graphEvent.currentState)
  } else if (GraphEventUtils.isRelation(selectingCell)) {
    if (GraphEventUtils.isDiagramCell(currentCell)) return;
    // 添加关系情况
    graphEvent.addEdge(me, graphEvent.currentState)
  } else if (!GraphEventUtils.isDiagramCell(currentCell)) {
    // 添加包含关系情况
    graphEvent.addChildrenNode(me, graphEvent.currentState)
  }
  me.consume();
}

GraphEvent.prototype.getMouseEnterModelTypeIgnore = function () {
  const selectedNode = window.zTreeObj?.currentSelectedNode
  // 普通绘图和思维导图，连线等不受限制
  if (['mind_map', 'general_drawing'].includes(selectedNode.type)) return true
  // 状态图、IDEf0图，连线等不受限制
  if (['DIAGRAM_SYSML_STATEMACHINE', 'DIAGRAM_IDEF0'].includes(selectedNode.modelType)) {
    return true
  }
  return !!window.isGuideModel;
}

GraphEvent.prototype.dragEnter = function (evt, state, graphEvent) {
  if (state.cell.style.indexOf('group') > -1) return

  if ((window.isAddingStatus || graphEvent.graph.connectionHandler.isConnecting()) && state.cell.id !== '3') {
    graphEvent.graph.graphEventDragEntered = true

    // 检查状态
    graphEvent.enabled = graphEvent.getMouseEnterEnabled(state.cell)

    myEditor.actions.get('connectionPoints').funct(graphEvent.enabled);

    graphEvent.highlight.setHighlightColor(graphEvent.enabled ? '#0075ff' : '#ff0000')

    graphEvent.highlight.highlight(state)

    graphEvent.graph.setCellSelectable(false)
  }
}

GraphEvent.prototype.dragLeave = function (evt, state, graphEvent) {
  if (graphEvent.highlight != null) {
    graphEvent.highlight.hide()
    graphEvent.graph.setCellSelectable(true)
    graphEvent.graph.graphEventDragEntered = false
  }
}

/**
 * @description 获取当前鼠标下的元素是否可选择
 * @param {mxCell} currentCell 当前鼠标下的元素 target
 * @param {mxCell=} selectingCell 当前正在操作的元素
 * @returns {boolean} 返回布尔值，false则不可选择
 */
GraphEvent.prototype.getMouseEnterEnabled = function (currentCell, selectingCell) {
  if (!window.zTreeObj) return true

  selectingCell = selectingCell || this.graph.selectingCell

  if (!selectingCell) return false

  if (this.getMouseEnterModelTypeIgnore()) return true

  return this.allowCellInsert(currentCell, selectingCell)
}

GraphEvent.prototype.createToBeRemovedCell = function (graph, me) {
  const pointCell = new mxCell("", new mxGeometry(0, 0, 0.001, 0.001), 'whiteSpace=wrap;html=1;aspect=fixed;');
  const {x, y} = graph.getPointForEvent(me.evt)
  pointCell.geometry.x = x
  pointCell.geometry.y = y
  pointCell.vertex = true;
  // 需要移除的，即只起到辅助功能，用完马上移除掉
  pointCell.toBeRemoved = 'toBeRemoved'
  return window.myEditor.editor.graph.addCells([pointCell], undefined, undefined, undefined, undefined, undefined, true)
}

/**
 * @description 创建自由连线
 */
GraphEvent.prototype.createAutoConnect = function (graphEvent, me) {
  window.isAddingStatus = true
  const addedCell = graphEvent.createToBeRemovedCell(graphEvent.graph, me)
  const selectingCell = graphEvent.graph.selectingCell
  graphEvent.isConnecting = false
  graphEvent.startConnect(graphEvent.graph, me, graphEvent.graph.view.getState(addedCell[0]), selectingCell, 0)
  // graphEvent.exitAddPattern()
}

/**
 * @description 删除辅助元素
 * @param {Graph}} graph graph
 * @param {mxCell} edge 关系线
 * @param {Array[mxCell]} cells 要删除的cells
 */
GraphEvent.prototype.removeToBeRemoved = function (graph, edge, cells) {
  if (Array.isArray(cells)) {
    cells.map(cell => {
      graph.model.beginUpdate()
      try {
        const state = graph.view.getState(edge)
        if (state) {
          const geo = new mxGeometry(state.x, state.y, state.width, state.height)
          graph.getModel().setGeometry(edge, geo)
        }

        // graph.removeCells(cells, false)
        // IdefoSelect
        window.drawioBus && window.drawioBus.$emit('IdefoSelect', edge)
      } finally {
        graph.model.endUpdate()
        this.graph.selectingCell = null
      }
    });
  }
}

/**
 * 子元素适应父元素宽度
 * @param cell {mxCell}
 */
GraphEvent.prototype.fitChildren = function (cell) {
  var children = cell.children;

  if (!children || children.length === 0) return;

  let changed = false;

  children
    .forEach(function (c) {
      if (c.isVertex() && c.style.indexOf(mxConstants.STYLE_FIT_WIDTH + '=1') > -1) {
        changed = true;
        var geo = c.geometry.clone()

        geo.width = cell.geometry.width;

        c.setGeometry(geo);
      }
    });

  if (changed) {
    this.graph.refresh();
  }
}

/**
 * cell resize事件监听处理
 * @param graph
 * @param mxEventObject
 */
GraphEvent.prototype.cellsResize = function (graph, mxEventObject) {
  var cells = mxEventObject.getProperty('cells');
  if (!cells.length) return;

  var cell = cells[0];
}

GraphEvent.prototype.cellsRemoved = function (sender, me) {
  this.diagramSizeWather.cellsRemoved(me.getProperty('cells'))
}


/**
 * 退出创建状态
 */
GraphEvent.prototype.exitAddPattern = function () {
  this.isConnecting = true
  setTimeout(() => {
    this.isConnecting = false
  }, 300)
  const sidebar = window.myEditor && window.myEditor.sidebar
  if (window.isPokePatttern === false) {
    window.isAddingStatus = false
    sidebar && sidebar.lastClickElt && (sidebar.lastClickElt.style.background = '');
    this.tooltip && this.tooltip.parentNode.removeChild(this.tooltip)
    window.myEditor.editor.graph.setCellSelectable(true)

    myEditor.actions.get('connectionPoints').funct(false);

    if (document.querySelector('#cover')) {
      document.querySelector('#cover').parentNode.removeChild(document.querySelector('#cover'))
    }
  }
  sidebar && sidebar.updateToolStatus()
}
